// 文件路径: dbms/parser/SqlLexer.java
package com.dbms.parser;

import com.dbms.common.Constants;
import com.dbms.common.Position;
import com.dbms.common.SqlError;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * SQL词法分析器（Lexer）
 * 核心功能：将原始SQL字符串解析为结构化的Token序列，为后续语法分析（Parser）提供输入
 * 处理逻辑：按字符类型（关键字、标识符、常量、操作符、分隔符等）分类识别，生成包含位置信息的Token
 *
 * 【已修复】支持多字符操作符识别（如>=, <=, !=, <>），避免误拆分为单字符操作符
 * 【已增强】新增对SQL单行注释（-- 注释内容）的支持，注释内容会被忽略不生成Token
 */
public class SqlLexer {
    // 原始SQL输入字符串
    private final String input;
    // 输入字符串的长度，用于边界判断
    private final int length;
    // 当前处理的字符位置索引（从0开始）
    private int currentPosition;
    // 当前字符所在的行号（从1开始，用于错误定位）
    private int currentLine;
    // 当前字符所在的列号（从1开始，用于错误定位）
    private int currentColumn;
    // 存储解析生成的Token列表
    private final List<Token> tokens = new ArrayList<>();
    // 存储词法分析过程中产生的错误（如非法字符、未闭合字符串等）
    private final List<SqlError> errors = new ArrayList<>();

    // 存储SQL关键字集合（如SELECT, CREATE, WHERE等），用于区分关键字和普通标识符
    private static final Set<String> KEYWORDS = new HashSet<>();
    // 存储数据类型关键字集合（如INT, VARCHAR等），单独分类便于语法分析
    private static final Set<String> TYPES = new HashSet<>();

    /**
     * 静态初始化块：加载SQL关键字和数据类型到集合中
     * 关键字统一按大写存储，解析时会将标识符转为大写后匹配
     */
    static {
        // 初始化关键字集合（涵盖DDL、DML、DCL等常用SQL语句关键字）
        KEYWORDS.add("CREATE"); KEYWORDS.add("TABLE"); KEYWORDS.add("INSERT"); KEYWORDS.add("INTO"); KEYWORDS.add("VALUES");
        KEYWORDS.add("SELECT"); KEYWORDS.add("FROM"); KEYWORDS.add("WHERE"); KEYWORDS.add("DELETE"); KEYWORDS.add("PRIMARY");
        KEYWORDS.add("KEY"); KEYWORDS.add("UPDATE"); KEYWORDS.add("SET"); KEYWORDS.add("JOIN"); KEYWORDS.add("ON");
        KEYWORDS.add("ORDER"); KEYWORDS.add("GROUP"); KEYWORDS.add("BY"); KEYWORDS.add("ASC"); KEYWORDS.add("DESC");
        KEYWORDS.add("BACKUP"); KEYWORDS.add("DATABASE"); KEYWORDS.add("TO"); KEYWORDS.add("USER"); KEYWORDS.add("IDENTIFIED");
        KEYWORDS.add("DROP"); KEYWORDS.add("GRANT"); KEYWORDS.add("REVOKE"); KEYWORDS.add("BEGIN"); KEYWORDS.add("TRANSACTION");
        KEYWORDS.add("COMMIT"); KEYWORDS.add("ROLLBACK"); KEYWORDS.add("EXPLAIN"); KEYWORDS.add("ANALYZE"); KEYWORDS.add("LIMIT");
        KEYWORDS.add("HAVING");

        // 初始化数据类型集合（可根据数据库支持的类型扩展）
        TYPES.add("INT");
        TYPES.add("VARCHAR");
    }

    /**
     * 构造方法：初始化词法分析器
     * @param input 待解析的SQL字符串
     */
    public SqlLexer(String input) {
        this.input = input;
        this.length = input.length();
        this.currentPosition = 0; // 从字符串起始位置开始解析
        this.currentLine = 1;     // 初始行号为1
        this.currentColumn = 1;   // 初始列号为1
    }

    /**
     * 核心方法：执行词法分析，将输入SQL转换为Token列表
     * @return 解析生成的Token列表（包含EOF作为结束标记）
     */
    public List<Token> tokenize() {
        // 循环处理每个字符，直到到达输入末尾
        while (currentPosition < length) {
            char c = peek(); // 查看当前字符（不移动指针）

            if (Character.isWhitespace(c)) {
                // 处理空白字符（空格、制表符、换行等），仅更新位置不生成Token
                consume();
                continue;
            }
            // --- 【新增】注释处理逻辑 ---
            else if (c == '-' && peekNext() == '-') {
                // 识别单行注释（-- 开头），跳过注释内容
                processComment();
                continue;
            }
            // --- 【新增结束】 ---
            else if (Character.isLetter(c) || c == '_') {
                // 处理标识符或关键字（以字母或下划线开头）
                processIdentifierOrKeyword();
            }
            else if (Character.isDigit(c)) {
                // 处理数字常量（整数）
                processNumber();
            }
            else if (c == '\'' || c == '"') {
                // 处理字符串常量（单引号或双引号包裹）
                processString(c);
            }
            else if (isOperatorStart(c)) {
                // 处理操作符（如=, +, >=等）
                processOperator();
            }
            else if (isDelimiter(c)) {
                // 处理分隔符（如括号、逗号、分号等）
                processDelimiter();
            }
            else {
                // 处理非法字符，记录错误并跳过该字符
                errors.add(new SqlError(
                        Constants.ErrorType.LEXICAL_ERROR,
                        new Position(currentLine, currentColumn, currentPosition),
                        "非法字符: " + c
                ));
                consume();
            }
        }

        // 解析结束，添加EOF（文件结束）Token作为终止标记
        tokens.add(new Token(TokenType.EOF, "", new Position(currentLine, currentColumn, currentPosition)));
        return tokens;
    }

    /**
     * 【新增】处理SQL单行注释
     * 逻辑：从"--"开始，持续消费字符直到换行符（\n）或文件末尾，注释内容不生成Token
     */
    private void processComment() {
        // 跳过注释内容（不包括换行符，换行符会在consume()中处理行号更新）
        while (currentPosition < length && peek() != '\n') {
            consume();
        }
    }


    // --- 【核心修复】处理操作符（支持多字符操作符） ---
    /**
     * 处理操作符，支持单字符（如+, -, =）和多字符（如>=, <=, !=, <>）操作符
     * 优先识别多字符操作符，避免拆分为单字符（如将">="识别为一个操作符而非">"和"="）
     */
    private void processOperator() {
        int startColumn = currentColumn; // 记录操作符起始列号（用于错误定位）
        int startIndex = currentPosition; // 记录操作符起始位置索引
        char first = peek(); // 第一个字符
        char second = peekNext(); // 第二个字符（用于判断多字符操作符）

        String op = null;
        // 处理两字符操作符：>=, <=, !=
        if (second == '=') {
            if (first == '>' || first == '<' || first == '!') {
                op = "" + consume() + consume(); // 消费两个字符组成操作符
            }
        }
        // 处理两字符操作符：<>（不等于，与!=等价）
        else if (first == '<' && second == '>') {
            op = "" + consume() + consume(); // 消费两个字符组成操作符
        }

        if (op != null) {
            // 生成多字符操作符Token
            Position position = new Position(currentLine, startColumn, startIndex);
            tokens.add(new Token(TokenType.OPERATOR, op, position));
        } else {
            // 处理单字符操作符（如+, -, *, /, =等）
            char c = consume();
            Position position = new Position(currentLine, startColumn, startIndex);
            tokens.add(new Token(TokenType.OPERATOR, String.valueOf(c), position));
        }
    }

    /**
     * 判断字符是否为操作符的起始字符
     * @param c 待判断的字符
     * @return 是操作符起始字符返回true，否则返回false
     */
    private boolean isOperatorStart(char c) {
        return c == '=' || c == '>' || c == '<' || c == '!' ||
                c == '+' || c == '-' || c == '*' || c == '/';
    }
    // --- 【修复结束】 ---


    /**
     * 处理标识符或关键字
     * 逻辑：识别以字母或下划线开头，后跟字母、数字或下划线的字符串，
     *       根据是否在关键字集合中，生成KEYWORD、TYPE或IDENTIFIER类型的Token
     */
    private void processIdentifierOrKeyword() {
        int startColumn = currentColumn;
        int startIndex = currentPosition;
        StringBuilder lexeme = new StringBuilder(); // 存储标识符的完整字符序列

        // 持续消费字符，直到遇到非字母、数字、下划线的字符
        while (currentPosition < length && (Character.isLetterOrDigit(peek()) || peek() == '_')) {
            lexeme.append(consume());
        }

        String value = lexeme.toString().toUpperCase(); // 转为大写用于关键字匹配
        Position position = new Position(currentLine, startColumn, startIndex);

        // 根据字符串类型生成对应Token
        if (KEYWORDS.contains(value)) {
            tokens.add(new Token(TokenType.KEYWORD, lexeme.toString(), position));
        } else if (TYPES.contains(value)) {
            tokens.add(new Token(TokenType.TYPE, lexeme.toString(), position));
        } else {
            tokens.add(new Token(TokenType.IDENTIFIER, lexeme.toString(), position));
        }
    }

    /**
     * 处理数字常量（仅支持整数）
     * 逻辑：识别连续数字序列，生成CONSTANT类型的Token
     */
    private void processNumber() {
        int startColumn = currentColumn;
        int startIndex = currentPosition;
        StringBuilder lexeme = new StringBuilder();

        // 持续消费数字字符
        while (currentPosition < length && Character.isDigit(peek())) {
            lexeme.append(consume());
        }

        Position position = new Position(currentLine, startColumn, startIndex);
        tokens.add(new Token(TokenType.CONSTANT, lexeme.toString(), position));
    }


    /**
     * 处理字符串常量（单引号或双引号包裹）
     * 逻辑：识别引号包裹的字符序列，检查是否闭合，生成CONSTANT类型的Token
     * @param quote 引号类型（'或"），用于匹配闭合引号
     */
    private void processString(char quote) {
        int startColumn = currentColumn;
        int startIndex = currentPosition;
        StringBuilder lexeme = new StringBuilder();
        lexeme.append(consume()); // 先消费起始引号

        // 持续消费字符直到遇到闭合引号
        while (currentPosition < length && peek() != quote) {
            if (peek() == '\n') {
                // 遇到换行符仍未找到闭合引号，记录未闭合字符串错误
                errors.add(new SqlError(
                        Constants.ErrorType.LEXICAL_ERROR,
                        new Position(currentLine, startColumn, startIndex),
                        "未闭合的字符串"
                ));
                return;
            }
            lexeme.append(consume());
        }

        // 到达文件末尾仍未找到闭合引号，记录错误
        if (currentPosition >= length) {
            errors.add(new SqlError(
                    Constants.ErrorType.LEXICAL_ERROR,
                    new Position(currentLine, startColumn, startIndex),
                    "未闭合的字符串"
            ));
            return;
        }

        // 消费闭合引号，完成字符串解析
        lexeme.append(consume());
        Position position = new Position(currentLine, startColumn, startIndex);
        tokens.add(new Token(TokenType.CONSTANT, lexeme.toString(), position));
    }

    /**
     * 处理分隔符（如括号、逗号、分号、点号等）
     * 逻辑：单个字符作为分隔符，生成DELIMITER类型的Token
     */
    private void processDelimiter() {
        int startColumn = currentColumn;
        int startIndex = currentPosition;
        char c = consume(); // 消费分隔符字符
        Position position = new Position(currentLine, startColumn, startIndex);
        tokens.add(new Token(TokenType.DELIMITER, String.valueOf(c), position));
    }

    /**
     * 判断字符是否为分隔符
     * @param c 待判断的字符
     * @return 是分隔符返回true，否则返回false
     */
    private boolean isDelimiter(char c) {
        return c == '(' || c == ')' || c == ',' || c == ';' || c == '.';
    }

    /**
     * 查看当前位置的字符（不移动指针）
     * @return 当前字符，若已到末尾返回'\0'
     */
    private char peek() {
        if (currentPosition >= length) {
            return '\0';
        }
        return input.charAt(currentPosition);
    }

    /**
     * 查看下一个位置的字符（不移动指针）
     * 用于判断多字符操作符（如查看当前字符后是否跟随'='组成>=）
     * @return 下一个字符，若已到末尾返回'\0'
     */
    private char peekNext() {
        if (currentPosition + 1 >= length) {
            return '\0';
        }
        return input.charAt(currentPosition + 1);
    }

    /**
     * 消费当前位置的字符（移动指针并更新行号和列号）
     * @return 当前消费的字符
     */
    private char consume() {
        char c = input.charAt(currentPosition);
        currentPosition++; // 移动到下一个字符
        currentColumn++;   // 列号递增

        // 若遇到换行符，更新行号并重置列号
        if (c == '\n') {
            currentLine++;
            currentColumn = 1;
        }

        return c;
    }

    /**
     * 获取词法分析过程中产生的错误列表
     * @return 错误信息列表（包含错误类型、位置和描述）
     */
    public List<SqlError> getErrors() {
        return errors;
    }
}